package uform.model.value;

import thx.error.Error;
import thx.util.Result;
import uform.util.DateGranularity;
using thx.util.Results;
using DateTools;

class DateType extends ValueType<Date>
{
	static inline var PYEAR = "(?:20|19)\\d{2}";
	static inline var PMONTH = "1[0-2]|0?[1-9]";
	static inline var PDAY = "31|0?[1-9]|[1-2][0-9]";
	static inline var PHOUR = "[01]?[0-9]|2[0-4]";
	static inline var PMINUTE = "[0-5]?[0-9]";
	static inline var PSECOND = PMINUTE;
	static var _reYear = new EReg("^\\s*(" + PYEAR + ")\\s*", "m");
	static var _reMonth = new EReg("^\\s*(" + PYEAR + ")-(" + PMONTH + ")\\s*", "m");
	static var _reDay = new EReg("^\\s*(" + PYEAR + ")-(" + PMONTH + ")-(" + PDAY + ")\\s*", "m");
	static var _reHour = new EReg("^\\s*(" + PYEAR + ")-(" + PMONTH + ")-(" + PDAY + ")T(" + PHOUR + ")\\s*", "m");
	static var _reMinute = new EReg("^\\s*(" + PYEAR + ")-(" + PMONTH + ")-(" + PDAY + ")T(" + PHOUR + "):(" + PMINUTE + ")\\s*", "m");
	static var _reSecond = new EReg("^\\s*(" + PYEAR + ")-(" + PMONTH + ")-(" + PDAY + ")T(" + PHOUR + "):(" + PMINUTE + "):(" + PSECOND + ")Z?\\s*", "m");

	public var granularity : DateGranularity;
	
	public function new(?granularity : DateGranularity)
	{
		super();
		this.granularity = null == granularity ? Second : granularity;
	}
	
	override function validateType(value : Dynamic) : Result
	{
		if (Std.is(value, Date))
			return Result.Ok;
		else
			return "expected type {0} but was {1}".failure(["Date", Types.typeName(value)]);
	}

	public static function format(granularity) : String
	{
		switch(granularity)
		{
			case Year:
				return "%Y";
			case Month:
				return "%Y-%m";
			case Day:
				return "%Y-%m-%d";
			case Hour:
				return "%Y-%m-%dT%H";
			case Minute:
				return "%Y-%m-%dT%H:%M";
			case Second:
				return "%Y-%m-%dT%H:%M:%S";
		}
	}
	
	override function _parse(s : String, ?ref : { s : String }) : Date
	{
		var year = 0, month = 0, day = 1, hour = 0, minute = 0, second = 0, millis = 0;
		switch(granularity)
		{
			case Year:
				if (!_reYear.match(s))
					return throw "expected a valid year date but was '{0}'".failure(s);
				year = Std.parseInt(_reYear.matched(1));
			case Month:
				if (!_reMonth.match(s))
					return throw "expected a valid year-month date but was '{0}'".failure(s);
				year  = Std.parseInt(_reMonth.matched(1));
				month = Std.parseInt(_reMonth.matched(2)) - 1;
			case Day:
				if (!_reDay.match(s))
					return throw "expected a valid year-month-day date but was '{0}'".failure(s);
				year  = Std.parseInt(_reDay.matched(1));
				month = Std.parseInt(_reDay.matched(2)) - 1;
				day   = Std.parseInt(_reDay.matched(3));
			case Hour:
				if (!_reHour.match(s))
					return throw "expected a valid year-month-dayThour date but was '{0}'".failure(s);
				year  = Std.parseInt(_reHour.matched(1));
				month = Std.parseInt(_reHour.matched(2)) - 1;
				day   = Std.parseInt(_reHour.matched(3));
				hour  = Std.parseInt(_reHour.matched(4));
			case Minute:
				if (!_reMinute.match(s))
					return throw "expected a valid year-month-dayThour:minute date but was '{0}'".failure(s);
				year   = Std.parseInt(_reMinute.matched(1));
				month  = Std.parseInt(_reMinute.matched(2)) - 1;
				day    = Std.parseInt(_reMinute.matched(3));
				hour   = Std.parseInt(_reMinute.matched(4));
				minute = Std.parseInt(_reMinute.matched(5));
			case Second:
				if (!_reSecond.match(s))
					return throw "expected a valid year-month-dayThour:minute:second date but was '{0}'".failure(s);
				year   = Std.parseInt(_reSecond.matched(1));
				month  = Std.parseInt(_reSecond.matched(2)) - 1;
				day    = Std.parseInt(_reSecond.matched(3));
				hour   = Std.parseInt(_reSecond.matched(4));
				minute = Std.parseInt(_reSecond.matched(5));
				second = Std.parseInt(_reSecond.matched(6));
		}
		// TODO: add here check for correct month/day combinations, bisext
		return new Date(year, month, day, hour, minute, second);
	}
	
	override function render(value : Date)
	{
		var f = format(granularity);
		return thx.culture.FormatDate.format(f, value, thx.cultures.EnUS.culture, true);
	}
}